package cn.cloud.all.security.oauth2.client.filter;

import cn.cloud.all.security.oauth2.client.exception.UserRedirectRequiredException;
import cn.cloud.all.security.oauth2.common.DefaultThrowableAnalyzer;
import cn.cloud.all.security.web.DefaultRedirectStrategy;
import cn.cloud.all.security.web.RedirectStrategy;
import cn.cloud.all.security.web.util.ThrowableAnalyzer;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

public class OAuth2ClientContextFilter implements Filter, InitializingBean {

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(redirectStrategy, "A redirect strategy must be supplied.");
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        request.setAttribute("currentUri", calculateCurrentUri(request));

        try {
            chain.doFilter(servletRequest, servletResponse);
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            // Try to extract a SpringSecurityException from the stacktrace
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer.getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain);
            if (redirect != null) {
                //跳转到授权服务器
                redirectUser(redirect, request, response);
            } else {
                if (ex instanceof ServletException) {
                    throw (ServletException) ex;
                }
                throw (RuntimeException) ex;
            }
        }
    }

    /**
     * 跳转到授权服务器
     * 授权服务器跳回到UI服务器原来的地址(带回来授权许可码),再次被OAuth2ClientAuthenticationProcessingFilter拦截发送获取accessToken
     * 经org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport#retrieveToken提交POST请求,获取到返回原来发请求处得到OAuth2AccessToken对象.
     */
    void redirectUser(UserRedirectRequiredException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String redirectUri = e.getRedirectUri();
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUri);
        Map<String, String> requestParams = e.getRequestParams();
        for (Map.Entry<String, String> param : requestParams.entrySet()) {
            builder.queryParam(param.getKey(), param.getValue());
        }

        if (e.getStateKey() != null) {
            builder.queryParam("state", e.getStateKey());
        }

        this.redirectStrategy.sendRedirect(request, response, builder.build().encode().toUriString());
    }

    String calculateCurrentUri(HttpServletRequest request) throws UnsupportedEncodingException {
        ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequest(request);
        // Now work around SPR-10172...
        String queryString = request.getQueryString();
        boolean legalSpaces = queryString != null && queryString.contains("+");
        if (legalSpaces) {
            builder.replaceQuery(queryString.replace("+", "%20"));
        }
        UriComponents uri;
        try {
            uri = builder.replaceQueryParam("code").build(true);
        } catch (IllegalArgumentException ex) {
            // ignore failures to parse the url (including query string). does't
            // make sense for redirection purposes anyway.
            return null;
        }
        String query = uri.getQuery();
        if (legalSpaces) {
            query = query.replace("%20", "+");
        }
        return ServletUriComponentsBuilder.fromUri(uri.toUri()).replaceQuery(query).build().toString();
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }
}
